/************************************************************************

								Heroes IV
					  Copyright 2000, The 3DO Company

	------------------------------------------------------------------
  					   scaled_adventure_map_drawer.cpp

	$Header: /game/scaled_adventure_map_drawer.cpp $

	$NoKeywords: $

 ************************************************************************/

#include "precompiled.h"
#pragma warning( disable: 4786 )

#include "scaled_adventure_map_drawer.h"

#include <assert.h>
#include <vector>

#include "abstract_bitmap.h"
#include "map_renderer.h"
#include "memory_bitmap.h"
#include "screen_point.h"

// --------------------------------------------------------------------------
// Unnamed namespace
// --------------------------------------------------------------------------

namespace
{

	t_screen_point const k_work_buffer_size( 256, 256 );

	typedef t_scaled_adventure_map_drawer::t_scale t_scale;
	int const k_scale_count = t_scaled_adventure_map_drawer::k_scale_count;

	// --------------------------------------------------------------------------
	// t_scale_properties struct
	// --------------------------------------------------------------------------

	struct t_scale_properties
	{
		int		factor;
		int		shift;
	};

	t_scale_properties const k_scale_properties_array[ k_scale_count ] =
	{
		{  1, 0 },
		{  2, 1 },
		{  4, 2 },
		{  8, 3 },
		{ 16, 4 },
	};

	// --------------------------------------------------------------------------
	// --------------------------------------------------------------------------
	void scaled_blt(
		t_abstract_bitmap16 const &	source,
		t_screen_point const &		source_pos,
		t_abstract_bitmap16 &		dest,
		t_screen_point const &		dest_pos,
		t_screen_point const &		size,
		t_scale						scale )
	{
		assert( scale >= 0 && scale < k_scale_count );
		int scale_factor = k_scale_properties_array[ scale ].factor;

		t_uint16 const * source_line_ptr = source.advance_line( source.get_data_ptr(), source_pos.y ) + source_pos.x;
		t_uint16 * dest_line_ptr = dest.advance_line( dest.get_data_ptr(), dest_pos.y ) + dest_pos.x;

		int y;
		for ( y = 0; y < size.y; ++y )
		{
			t_uint16 const * source_ptr = source_line_ptr;
			t_uint16 * dest_ptr = dest_line_ptr;

			int x;
			for ( x = 0; x < size.x; ++x )
			{
				*dest_ptr = *source_ptr;
				source_ptr += scale_factor;
				++dest_ptr;
			}

			source_line_ptr = source.advance_line( source_line_ptr, scale_factor );
			dest_line_ptr = dest.advance_line( dest_line_ptr );
		}
	}

} // Unnamed namespace

// --------------------------------------------------------------------------
// t_scaled_adventure_map_drawer::t_impl class
// --------------------------------------------------------------------------

class t_scaled_adventure_map_drawer::t_impl : public t_map_renderer::t_client
{
public:
	// Data members
	t_map_renderer		m_map_renderer;
	t_back_buffer_ptr	m_work_buffer_ptr;

	// Constructor
	t_impl(
		t_abstract_adventure_map const &	map,
		int									view_level,
		int									team_view );

	// Member functions
	virtual t_back_buffer_ptr	create_back_buffer(
									t_screen_point const &	view_size );
	virtual void				on_rects_dirtied(
									std::vector< t_screen_rect > const &	dirtied_rects );
};

// --------------------------------------------------------------------------
// t_scaled_adventure_map_drawer::t_impl members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
#pragma warning( push )
#pragma warning( disable: 4355 )
t_scaled_adventure_map_drawer::t_impl::t_impl(
	t_abstract_adventure_map const &	map,
	int									view_level,
	int									team_view )
	:	m_map_renderer( *this, map ),
		m_work_buffer_ptr( new t_memory_bitmap16( k_work_buffer_size.x, k_work_buffer_size.y ) )
{
	m_map_renderer.enable_animation( false );
	m_map_renderer.set_view_level( view_level );
	m_map_renderer.set_team_view( team_view );
}
#pragma warning( pop )

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_scaled_adventure_map_drawer::t_impl::t_back_buffer_ptr
t_scaled_adventure_map_drawer::t_impl::create_back_buffer(
	t_screen_point const &	view_size )
{
	assert( view_size.x <= k_work_buffer_size.x );
	assert( view_size.y <= k_work_buffer_size.y );
	return m_work_buffer_ptr;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_scaled_adventure_map_drawer::t_impl::on_rects_dirtied(
	std::vector< t_screen_rect > const &	dirtied_rects )
{
	std::vector< t_screen_rect >::const_iterator rect_end = dirtied_rects.end();
	std::vector< t_screen_rect >::const_iterator rect_iter = dirtied_rects.begin();
	for ( ; rect_iter != rect_end; ++rect_iter )
		m_map_renderer.update( *rect_iter );
}

// --------------------------------------------------------------------------
// t_scaled_adventure_map_drawer members
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_scaled_adventure_map_drawer::t_scaled_adventure_map_drawer(
	t_abstract_adventure_map const &	map,
	int									view_level,
	int									team_view)
	:	m_impl_ptr( new t_impl( map, view_level, team_view ) )
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_scaled_adventure_map_drawer::~t_scaled_adventure_map_drawer()
{
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int t_scaled_adventure_map_drawer::get_team_view() const
{
	return m_impl_ptr->m_map_renderer.get_team_view();
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int t_scaled_adventure_map_drawer::get_view_level() const
{
	return m_impl_ptr->m_map_renderer.get_view_level();
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_scaled_adventure_map_drawer::set_team_view( int new_team_view )
{
	m_impl_ptr->m_map_renderer.set_team_view( new_team_view );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_scaled_adventure_map_drawer::set_view_level( int new_view_level )
{
	m_impl_ptr->m_map_renderer.set_view_level( new_view_level );
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
void t_scaled_adventure_map_drawer::operator()(
	t_scale					scale,
	t_screen_rect const &	source_rect,
	t_abstract_bitmap16 &	dest,
	t_screen_point const &	dest_pos ) const
{
	assert( scale >= 0 && scale < k_scale_count );

	t_impl & impl = *m_impl_ptr;

	t_scale_properties const & scale_properties = k_scale_properties_array[ scale ];
	int scale_shift = scale_properties.shift;

	t_screen_rect unscaled_source_rect = source_rect << scale_shift;

	t_screen_rect work_rect;
	work_rect.top = unscaled_source_rect.top;
	work_rect.bottom = unscaled_source_rect.top + k_work_buffer_size.y;

	t_screen_point work_dest_pos;
	work_dest_pos.y = dest_pos.y;

	while ( work_rect.top < unscaled_source_rect.bottom )
	{
		work_rect.left = unscaled_source_rect.left;
		work_rect.right = unscaled_source_rect.left + k_work_buffer_size.x;
		work_dest_pos.x = dest_pos.x;
		while ( work_rect.left < unscaled_source_rect.right )
		{
			assert( intersect( unscaled_source_rect, work_rect ) );

			t_screen_rect clipped_rect = intersection( unscaled_source_rect, work_rect );
			impl.m_map_renderer.move_view( clipped_rect.top_left(), clipped_rect.size() );

			scaled_blt(
				*impl.m_work_buffer_ptr,
				t_screen_point( 0, 0 ),
				dest,
				work_dest_pos,
				clipped_rect.size() >> scale_shift,
				scale );

			work_rect.left = work_rect.right;
			work_rect.right += k_work_buffer_size.x;
			work_dest_pos.x += k_work_buffer_size.x >> scale_shift;
		}

		work_rect.top = work_rect.bottom;
		work_rect.bottom += k_work_buffer_size.y;
		work_dest_pos.y += k_work_buffer_size.y >> scale_shift;
	}

	impl.m_map_renderer.move_view( t_screen_point( 0, 0 ), t_screen_point( 0, 0 ) );
}

// --------------------------------------------------------------------------
// Non-member functions
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int get_factor( t_scaled_adventure_map_drawer::t_scale scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return k_scale_properties_array[ scale_id ].factor;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
int get_shift( t_scaled_adventure_map_drawer::t_scale scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return k_scale_properties_array[ scale_id ].shift;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_screen_point scale(
	t_screen_point const &					point,
	t_scaled_adventure_map_drawer::t_scale	scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return point >> k_scale_properties_array[ scale_id ].shift;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_screen_rect scale(
	t_screen_rect const &					rect,
	t_scaled_adventure_map_drawer::t_scale	scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return rect >> k_scale_properties_array[ scale_id ].shift;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_screen_point unscale(
	t_screen_point const &					point,
	t_scaled_adventure_map_drawer::t_scale	scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return point << k_scale_properties_array[ scale_id ].shift;
}

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
t_screen_rect unscale(
	t_screen_rect const &					rect,
	t_scaled_adventure_map_drawer::t_scale	scale_id )
{
	assert( scale_id >= 0 && scale_id < k_scale_count );
	return rect << k_scale_properties_array[ scale_id ].shift;
}
